"""
用户认证服务类
"""

import json
import logging
from typing import Optional

from redis.asyncio import Redis
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.core.config import Settings
from app.exceptions.user import UserAccountFrozenException, UserNotFoundException
from app.models.system import SysRole, SysUser
from app.schemas.auth import Token
from app.service.token_service import TokenService
from app.utils.pwd_utils import get_password_hash, verify_password

# 日志记录器
logger = logging.getLogger(__name__)


class AuthService:
    """用户认证服务类"""

    def __init__(self, redis: Redis, session: AsyncSession, token_service: TokenService, settings: Settings):
        """初始化认证服务

        Args:
            redis: Redis客户端
            session: 数据库会话
            token_service: 令牌服务
            settings: 配置设置
        """
        self.redis = redis
        self.session = session
        self.token_service = token_service
        self.settings = settings

    @classmethod
    def verify_password(cls, plain_password: str, hashed_password: str) -> bool:
        """
        验证明文密码与哈希密码是否匹配

        Args:
            plain_password: 明文密码
            hashed_password: 哈希密码

        Returns:
            bool: 密码是否匹配
        """
        return verify_password(plain_password, hashed_password)

    @classmethod
    def get_password_hash(cls, password: str) -> str:
        """
        获取密码的哈希值

        Args:
            password: 明文密码

        Returns:
            str: 哈希密码
        """
        return get_password_hash(password)

    async def authenticate_user(self, username: str, password: str) -> Optional[SysUser]:
        """
        验证用户身份

        Args:
            username: 用户名
            password: 明文密码

        Returns:
            SysUser: 用户对象，如果验证失败返回None
        """
        # 检查用户是否存在黑名单中
        if await self.token_service.is_user_blacklisted(username):
            raise UserAccountFrozenException()

        # 查询用户
        stmt = (
            select(SysUser)
            .where(SysUser.username == username, SysUser.is_deleted == 0)
            .options(selectinload(SysUser.roles).selectinload(SysRole.menus))
        )
        result = await self.session.execute(stmt)
        user: SysUser = result.scalar_one_or_none()

        # 检查用户是否存在且密码正确
        if not user:
            raise UserNotFoundException()

        # 检查用户状态
        if user.status != 1:
            raise UserAccountFrozenException()

        # 验证密码
        if not self.verify_password(password, user.password):
            raise UserNotFoundException()

        return user

    async def login(self, username: str, password: str) -> Token:
        """
        用户登录

        Args:
            username: 用户名
            password: 明文密码

        Returns:
            Token: 包含访问令牌和刷新令牌的Token对象
        """
        # 验证用户身份
        user = await self.authenticate_user(username, password)

        payload = {"sub": user.username, "user_id": user.id}

        # 生成令牌
        token = await self.token_service.create_tokens(payload)

        roles = [role.code for role in user.roles]
        permissions = [menu.perm for role in user.roles for menu in role.menus if menu.perm]

        # 存储用户信息到Redis, 改变用户信息时，需要同时更新Redis中的用户信息（role、menu）
        if roles or permissions:
            async with self.redis.pipeline(transaction=True) as pipe:
                if roles:
                    pipe.set(f"{self.settings.KEY_AUTH_USER_ROLE}:{user.id}", json.dumps(roles), ex=token.expires_in)
                if permissions:
                    pipe.set(
                        f"{self.settings.KEY_AUTH_USER_PERMISSION}:{user.id}",
                        json.dumps(permissions),
                        ex=token.expires_in,
                    )
                await pipe.execute()

        payload_access = self.token_service.decode_access_token(token.access_token)
        # payload_refresh = self.token_service.decode_refresh_token(token.refresh_token)
        async with self.redis.pipeline(transaction=True) as pipe:
            pipe.set(
                f"{self.settings.KEY_ACCESS_TOKEN_USER}:{payload_access['sub']}:{payload_access['jti']}",
                token.refresh_token,
                ex=payload_access["exp"],
            )
            # pipe.set(
            #     f"{self.settings.KEY_REFRESH_TOKEN_USER}:{payload_refresh['sub']}:{payload_refresh['jti']}",
            #     payload_access["jti"],
            #     ex=payload_refresh["exp"],
            # )
            await pipe.execute()

        return token

    async def refresh_token(self, refresh_token: str) -> Token:
        """
        刷新访问令牌

        Args:
            refresh_token: 刷新令牌

        Returns:
            Token: 包含新的访问令牌和刷新令牌的Token对象
        """
        token = await self.token_service.refresh_token(refresh_token)
        return token

    async def logout(self, access_token: str) -> bool:
        """
        用户登出

        Args:
            token: Token对象，包含访问令牌和刷新令牌

        Returns:
            bool: 登出是否成功
        """
        # 使用token_service提供的方法吊销访问令牌和刷新令牌
        success = await self.token_service.add_token_to_blacklist(access_token)

        payload = self.token_service.decode_access_token(access_token)

        refresh_token = await self.redis.get(f"{self.settings.KEY_ACCESS_TOKEN_USER}:{payload['sub']}:{payload['jti']}")
        await self.redis.delete(f"{self.settings.KEY_ACCESS_TOKEN_USER}:{payload['sub']}:{payload['jti']}")

        if success:
            await self.token_service.add_token_to_blacklist(refresh_token)
        return success

    async def get_current_user(self, token: str) -> SysUser:
        """
        获取当前用户

        Args:
            token: 访问令牌

        Returns:
            SysUser: 用户对象
        """
        # 1. 验证 access_token 本身有效
        if not await self.token_service.is_token_valid(token):
            raise UserAccountFrozenException()

        payload = self.token_service.decode_access_token(token)
        username = payload.get("sub")
        if not username:
            raise UserNotFoundException()

        stmt = (
            select(SysUser)
            .where(SysUser.username == username, SysUser.is_deleted == 0)
            .options(selectinload(SysUser.roles).selectinload(SysRole.menus), selectinload(SysUser.dept))
        )
        result = await self.session.execute(stmt)
        user = result.scalar_one_or_none()

        if user is None:
            raise UserNotFoundException()

        return user

    async def get_current_active_user(self, token: str) -> SysUser:
        """
        获取当前活动用户

        Args:
            token: 访问令牌

        Returns:
            SysUser: 用户对象
        """
        user = await self.get_current_user(token)
        if user.status != 1:
            raise UserAccountFrozenException()
        return user
